#pragma once
#include "coin/proto/coin_request.pb.h"
#include "coin/proto/coin_telemetry.pb.h"
#include "coin2/exchange/base/feed/subscriber.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/log/strategy_logger.h"

namespace coin2::strategy::util {
inline void SetFeedTimeToLatency(
    coin2::exchange::base::strategy_util::StrategyLogger* strat_logger,
    const coin2::feed::mgr::impl::FeedSubsystemManager* mgr,
    const coin2::exchange::base::feed::FeedUpdate& upd,
    coin::proto::LatencyProto::LatencyTag tag,
    const coin::proto::AccountRequestProto& acc_request,
    bool mark_end,
    bool write_latency_stats=false) {
  if (!strat_logger) {
    LOG(WARNING) << "strat logger didn't init.";
    return;
  }
  auto latency_recorder = strat_logger->mutable_latency_recorder();
  if (!latency_recorder) {
    LOG(ERROR) << "doesn't have latency recorder";
    return;
  }
  latency_recorder->Begin(tag);
  auto latency_context_id = latency_recorder->GetCurrentContextId();
  auto feed_publish_point = coin::proto::LifeOfSignal::RAW_FEED_EXCHANGE_PUBLISHED;
  if (upd.is_book()) {
    feed_publish_point = coin::proto::LifeOfSignal::RAW_BOOK_FEED_EXCHANGE_PUBLISHED;
  } else if (upd.is_trade()) {
    feed_publish_point = coin::proto::LifeOfSignal::RAW_TRADE_FEED_EXCHANGE_PUBLISHED;
  }
  CHECK_NE(mgr->GetCounter(), nullptr) << "Please enable feed message_counter in Driver configuraiton";
  auto raw_feed_received_number = mgr->GetCounter()->ReceivedNum();
  auto raw_feed_processed_number = mgr->GetCounter()->ProcessedNum();
  auto parsed_feed_published_number = mgr->GetCounter()->PublishedNum();
  auto parsed_feed_consumed_number = mgr->GetCounter()->ConsumedNum();
  latency_recorder->SetProtoAndTime(
      latency_context_id,
      feed_publish_point,
      upd.ts().exchange_publish_timestamp(),
      acc_request);
  latency_recorder->SetProtoAndTime(
      latency_context_id,
      coin::proto::LifeOfSignal::RAW_FEED_RECEIVED,
      upd.ts().raw_rx_timestamp(),
      acc_request);
  latency_recorder->SetProtoAndTime(
      latency_context_id,
      coin::proto::LifeOfSignal::RAW_FEED_RECEIVED_NUMBER,
      mgr->GetCounter()->ReceivedNum(),
      acc_request);
  latency_recorder->SetProtoAndTime(
      latency_context_id,
      coin::proto::LifeOfSignal::RAW_FEED_PENDING_NUMBER,
      raw_feed_received_number - raw_feed_processed_number,
      acc_request);
  int64_t raw_feed_parse_started = 0;
  auto parser_rx_ts = upd.ts().parser_rx_timestamp();
  auto main_rx_ts = upd.ts().main_rx_timestamp();
  bool used_parser_thread = (parser_rx_ts > 0) ? true : false;
  raw_feed_parse_started = (used_parser_thread) ? parser_rx_ts : main_rx_ts;
  latency_recorder->SetProtoAndTime(
      latency_context_id,
      coin::proto::LifeOfSignal::RAW_FEED_PARSE_STARTED,
      raw_feed_parse_started,
      acc_request);
  if (parsed_feed_published_number > 0) {
    latency_recorder->SetProtoAndTime(
        latency_context_id,
        coin::proto::LifeOfSignal::PARSED_FEED_PUBLISHED_NUMBER,
        parsed_feed_published_number,
        acc_request);
    if (parsed_feed_consumed_number > 0) {
      latency_recorder->SetProtoAndTime(
          latency_context_id,
          coin::proto::LifeOfSignal::PARSED_FEED_PENDING_NUMBER,
          parsed_feed_published_number - parsed_feed_consumed_number,
          acc_request);
    }
  }
  int64_t parsed_feed_published = 0;
  auto parser_tx_ts = upd.ts().parser_tx_timestamp();
  auto main_tx_ts = upd.ts().main_tx_timestamp();
  parsed_feed_published = (used_parser_thread) ? parser_tx_ts : main_tx_ts;
  int64_t parsed_feed_consumed_ts = (used_parser_thread) ? main_rx_ts : main_tx_ts;
  if (upd.is_book()) {
    latency_recorder->SetProtoAndTime(
        latency_context_id,
        coin::proto::LifeOfSignal::PARSED_BOOK_FEED_PUBLISHED,
        parsed_feed_published,
        acc_request);
    latency_recorder->SetProtoAndTime(
        latency_context_id,
        coin::proto::LifeOfSignal::ON_BOOK_FEED,
        parsed_feed_consumed_ts,
        acc_request);
  } else if (upd.is_trade()) {
    latency_recorder->SetProtoAndTime(
        latency_context_id,
        coin::proto::LifeOfSignal::PARSED_TRADE_FEED_PUBLISHED,
        parsed_feed_published,
        acc_request);
    latency_recorder->SetProtoAndTime(
        latency_context_id,
        coin::proto::LifeOfSignal::ON_TRADE_FEED,
        parsed_feed_consumed_ts,
        acc_request);
  }
  if (upd.is_book() || upd.is_trade()) {
    if (strat_logger->after_feature_calc_time.has_value()) {
      latency_recorder->SetProtoAndTime(
          latency_context_id,
          coin::proto::LifeOfSignal::BEFORE_MANAGE_MM_ORDERS,
          *(strat_logger->after_feature_calc_time),
          acc_request);
    }
  }
  if (write_latency_stats) {
    int64_t parser_latency = parsed_feed_published - raw_feed_parse_started;
    if (upd.is_book()) {
      strat_logger->UpdateAndLogLatencyWindow(
          latency_recorder->GetMutableLatencyWindow(),
          true,
          upd.timestamp(),
          upd.mea(),
          coin::proto::StrategyComponentLatencyStatsProto::BOOK_FEED_PARSER,
          parser_latency);
      if (strat_logger->after_feature_calc_time.has_value() &&
          strat_logger->before_feature_calc_time.has_value()) {
        strat_logger->UpdateAndLogLatencyWindow(
            latency_recorder->GetMutableLatencyWindow(),
            true,
            upd.timestamp(),
            upd.mea(),
            coin::proto::StrategyComponentLatencyStatsProto::BOOK_PRICER,
            *(strat_logger->after_feature_calc_time) -
            *(strat_logger->before_feature_calc_time));
      }
    } else if (upd.is_trade()) {
      strat_logger->UpdateAndLogLatencyWindow(
          latency_recorder->GetMutableLatencyWindow(),
          true,
          upd.timestamp(),
          upd.mea(),
          coin::proto::StrategyComponentLatencyStatsProto::TRADE_FEED_PARSER,
          parser_latency);
      if (strat_logger->after_feature_calc_time.has_value() &&
          strat_logger->before_feature_calc_time.has_value()) {
        strat_logger->UpdateAndLogLatencyWindow(
            latency_recorder->GetMutableLatencyWindow(),
            true,
            upd.timestamp(),
            upd.mea(),
            coin::proto::StrategyComponentLatencyStatsProto::TRADE_PRICER,
            *(strat_logger->after_feature_calc_time) -
            *(strat_logger->before_feature_calc_time));
      }
    }
    if (strat_logger->after_evaluation_time.has_value() &&
        strat_logger->before_evaluation_time.has_value()) {
      strat_logger->UpdateAndLogLatencyWindow(
          latency_recorder->GetMutableLatencyWindow(),
          true,
          upd.timestamp(),
          upd.mea(),
          coin::proto::StrategyComponentLatencyStatsProto::MODEL_EVALUATION,
          *(strat_logger->after_evaluation_time) -
          *(strat_logger->before_evaluation_time));
    }
  }
  if (mark_end) {
    latency_recorder->End(latency_context_id);
  }
}

}  // namespace coin2::strategy::util
